use serde::{Deserialize, Serialize};

use crate::{CompactSignatureBytes, PublicKeyBytes, PublicKeyHashBytes};

/// Opcode that is supported by the scripting language.
#[derive(Clone, PartialEq, Deserialize, Serialize)]
pub enum OpCode {
    OpDup,         // OP_DUP;         Dump the top item on the stack
    OpHash160, // OP_RIPEMD160;   Pop public key then calculate hash160 of the public key and push it to the stack
    OpEqualverify, // OP_EQUALVERIFY; Verify the top item on the stack is equal to the second top item
    OpChecksig, // OP_CHECKSIG;    Verify the top item on the stack is a valid signature for the second item on the stack

    OpData(Vec<u8>), // The OP_DATA represents data pushed onto the stack

    OpSignature(CompactSignatureBytes), // Push the signature hash to the stack
    OpPubKey(PublicKeyBytes),           // OP_PUBKEY; Push the public key to the stack
    OpPubKeyHash(PublicKeyHashBytes), // OP_PUBKEYHASH; Push the hash160 of the public key to the stack

    // Special opcodes, only recorded for debugging purposes
    OpBlockHeight(crate::BlockHeight), // Store block height in coinbase tx-in script
}

impl std::fmt::Display for OpCode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            OpCode::OpDup => write!(f, "OpDup"),
            OpCode::OpHash160 => write!(f, "OpHash160"),
            OpCode::OpEqualverify => write!(f, "OpEqualverify"),
            OpCode::OpChecksig => write!(f, "OpChecksig"),
            OpCode::OpData(data) => write!(f, "OpData({})", hex::encode(data)),
            OpCode::OpSignature(sig) => write!(f, "OpSignature({})", sig),
            OpCode::OpPubKey(pubkey) => write!(f, "OpPubKey({})", pubkey),
            OpCode::OpPubKeyHash(pubkey_hash) => write!(f, "OpPubKeyHash({})", pubkey_hash),
            OpCode::OpBlockHeight(height) => write!(f, "OpBlockHeight({})", height),
        }
    }
}

impl std::fmt::Debug for OpCode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self)
    }
}

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct Script(Vec<OpCode>);

impl std::fmt::Display for Script {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let opcodes: Vec<String> = self.0.iter().map(|opcode| opcode.to_string()).collect();
        write!(f, "{}", opcodes.join(" "))
    }
}

impl Script {
    pub fn new(opcodes: Vec<OpCode>) -> Self {
        Self(opcodes)
    }

    pub fn new_empty() -> Self {
        Self(vec![])
    }

    pub fn iter(&self) -> impl Iterator<Item = &OpCode> {
        self.0.iter()
    }

    pub fn push(&mut self, opcode: OpCode) {
        self.0.push(opcode);
    }

    pub fn length(&self) -> usize {
        self.0.len()
    }

    pub fn extend(&mut self, other: Script) {
        self.0.extend(other.0);
    }

    pub fn as_opcodes(&self) -> &Vec<OpCode> {
        &self.0
    }

    pub fn size(&self) -> usize {
        let tmp = bincode::serialize(&self.0).unwrap();
        tmp.len()

        // self.0
        //     .iter()
        //     .map(|opcode| {
        //         let item = bincode::serialize(opcode).unwrap();
        //         item.len()
        //     })
        //     .sum()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_serde_json_opcode() {
        let opcode = OpCode::OpDup;
        assert_eq!(serde_json::to_string(&opcode).unwrap(), "\"OpDup\"");
        assert_eq!(
            serde_json::from_str::<OpCode>("{\"OpDup\":null}").unwrap(),
            opcode
        );
        assert_eq!(serde_json::from_str::<OpCode>("\"OpDup\"").unwrap(), opcode);

        let opcode = OpCode::OpData(vec![
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
            0x0F, 0x10,
        ]);

        let json_str = "{\"OpData\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}";
        assert_eq!(serde_json::to_string(&opcode).unwrap(), json_str);
        assert_eq!(serde_json::from_str::<OpCode>(json_str).unwrap(), opcode);
    }

    #[test]
    fn test_serde_bincode_opcode() {
        let opcode = OpCode::OpDup;
        assert_eq!(bincode::serialize(&opcode).unwrap(), vec![0, 0, 0, 0]);
        assert_eq!(
            bincode::deserialize::<OpCode>(&[0, 0, 0, 0]).unwrap(),
            opcode
        );

        let opcode = OpCode::OpHash160;
        assert_eq!(bincode::serialize(&opcode).unwrap(), vec![1, 0, 0, 0]);
        assert_eq!(
            bincode::deserialize::<OpCode>(&[1, 0, 0, 0]).unwrap(),
            opcode
        );

        let opcode = OpCode::OpEqualverify;
        assert_eq!(bincode::serialize(&opcode).unwrap(), vec![2, 0, 0, 0]);
        assert_eq!(
            bincode::deserialize::<OpCode>(&[2, 0, 0, 0]).unwrap(),
            opcode
        );

        let opcode = OpCode::OpChecksig;
        assert_eq!(bincode::serialize(&opcode).unwrap(), vec![3, 0, 0, 0]);
        assert_eq!(
            bincode::deserialize::<OpCode>(&[3, 0, 0, 0]).unwrap(),
            opcode
        );

        let opcode = OpCode::OpData(vec![1, 2, 3, 4, 5, 6]);

        let vec = vec![4, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6];
        assert_eq!(bincode::serialize(&opcode).unwrap(), vec);
        assert_eq!(bincode::deserialize::<OpCode>(&vec).unwrap(), opcode);
    }

    #[test]
    fn test_serde_bincode_opcode_signature() {
        #[rustfmt::skip]
        let sig_bytes: [u8; 64] = [
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
            17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
            33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
            49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
        ];

        let opcode = OpCode::OpSignature(CompactSignatureBytes::new(sig_bytes));

        let mut vec = vec![5, 0, 0, 0];
        vec.extend(sig_bytes);
        assert_eq!(bincode::serialize(&opcode).unwrap(), vec);
        assert_eq!(bincode::deserialize::<OpCode>(&vec).unwrap(), opcode);
    }

    #[test]
    fn test_serde_bincode_opcode_pubkey() {
        #[rustfmt::skip]
        let pubkey_bytes: [u8; 33] = [
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
            17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
            33,
        ];
        let opcode = OpCode::OpPubKey(PublicKeyBytes::new(pubkey_bytes));

        let mut vec = vec![6, 0, 0, 0];
        vec.extend(pubkey_bytes);
        assert_eq!(bincode::serialize(&opcode).unwrap(), vec);
        assert_eq!(bincode::deserialize::<OpCode>(&vec).unwrap(), opcode);
    }

    #[test]
    fn test_serde_bincode_opcode_pubkey_hash() {
        let pub_key_hash_bytes: [u8; 20] = [
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
        ];
        let pub_key_hash = PublicKeyHashBytes::new(pub_key_hash_bytes);

        let opcode = OpCode::OpPubKeyHash(pub_key_hash);

        let mut vec = vec![7, 0, 0, 0];
        vec.extend(pub_key_hash_bytes);
        assert_eq!(bincode::serialize(&opcode).unwrap(), vec);
        assert_eq!(bincode::deserialize::<OpCode>(&vec).unwrap(), opcode)
    }
}
